package cn.cas.mango.util.RequestUtil;

import cn.cas.mango.dto.HotSpotNews;
import cn.cas.mango.dto.param.HotSpotNewsParam;
import cn.cas.mango.service.ConfigService;
import cn.cas.mango.service.PartitionIndexService;
import cn.cas.mango.util.TimeUtil;
import cn.cas.mango.util.string.StringUtils;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch.core.CountRequest;
import co.elastic.clients.elasticsearch.core.CountResponse;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.json.JsonData;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 处理Elasticsearch热点列表详细请求的工具类。
 */
@Component
@Slf4j
public class HotListDetailRequest {

  @Autowired
  ElasticsearchClient elasticsearchClient;


  @Autowired
  ConfigService configService;

  private List<FieldValue> fieldValueList = new ArrayList<>();

  {
    String [] strings = new String[]{"广西", "湖北", "北京", "湖南", "天津", "广东", "河北", "海南", "山西", "重庆", "内蒙古", "四川", "辽宁", "贵州", "吉林", "云南", "黑龙江", "陕西", "上海", "甘肃", "江苏", "青海", "浙江", "宁夏", "福建", "新疆", "江西", "新疆兵团", "山东", "河南", "西藏", "安徽"}    ;
    Arrays.asList(strings).stream().map(x->FieldValue.of(x)).forEach(x->fieldValueList.add(x));
  }

  /**
   * 将给定的参数封装到BoolQuery.Builder中。
   *
   * @param hotSpotNewsParam 热点新闻的参数
   * @return 包含给定参数的BoolQuery.Builder
   */
  private BoolQuery.Builder wrapBool(HotSpotNewsParam hotSpotNewsParam) {
    String startDate = hotSpotNewsParam.getStartDate();
    String endDate = hotSpotNewsParam.getEndDate();
    BoolQuery.Builder bool = new BoolQuery.Builder();
    String area = hotSpotNewsParam.getArea();
    List<String> areaList = new ArrayList<>();
    if (!Strings.isBlank(area)) {
      areaList = Arrays.asList(area.split(","));
    }
    List<String> typeList = new ArrayList<>();
    if (!Strings.isBlank(hotSpotNewsParam.getType())) {
      typeList = Arrays.asList(hotSpotNewsParam.getType().split(","));
    }

    bool.filter(f -> f.range(r -> r.field("fssj").gte(JsonData.of(startDate))
        .lte(JsonData.of(endDate)))).
        filter(f -> f.terms(t -> t.field("lyss").terms(query->query.value(fieldValueList))
        ));

    if (!areaList.isEmpty()) {
      List<String> finalAreaList = areaList;
      bool.filter(f -> f.terms(t -> t.field("area").terms(query->query.value(
          finalAreaList.stream().map(x->FieldValue.of(x)).collect(Collectors.toList())
      ))));
    }

    if (!typeList.isEmpty()) {
      List<String> finalTypeList = typeList;
      bool.filter(f -> f.terms(t -> t.field("ssly").terms(query->query.value(
          finalTypeList.stream().map(x->FieldValue.of(configService.area2number.getOrDefault(x, ""))).collect(Collectors.toList())
      ))));
    }
    return bool;
  }





  /**
   * 使用给定的参数构建SearchRequest.Builder。
   *
   * @param hotSpotNewsParam 热点新闻的参数
   * @param from 搜索的起始索引
   * @param size 搜索结果的大小
   * @return 包含给定参数的SearchRequest.Builder
   */
  public SearchRequest.Builder buildRequest(HotSpotNewsParam hotSpotNewsParam, int from, int size) {
    SearchRequest.Builder builder = new SearchRequest.Builder();
    BoolQuery.Builder bool = wrapBool(hotSpotNewsParam);
    String startDate = StringUtils.transTime2Date(hotSpotNewsParam.getStartDate());
    String endDate = StringUtils.transTime2Date(hotSpotNewsParam.getEndDate());
    String index = configService.getIndex();
//    index = partitionIndexService.partitionIndex(index, startDate, endDate);

    builder.index(index).query(q->q.bool(bool.build())).from(from).size(size);
    return builder;
  }

  /**
   * 获取匹配给定参数的文档数量。
   *
   * @param hotSpotNewsParam 热点新闻的参数
   * @return 匹配给定参数的文档数量
   */
  public long getCount(HotSpotNewsParam hotSpotNewsParam) {
    CountRequest.Builder countSearch = new CountRequest.Builder().index(configService.getIndex());
    BoolQuery.Builder bool = wrapBool(hotSpotNewsParam);
    CountRequest.Builder query = countSearch.query(q -> q.bool(bool.build()));
    TimeUtil timeUtil = new TimeUtil();
    try {
      CountRequest build = query.build();
      log.info("【uuid: {}】 get [startDate: {} endDate: {}] count request: {}", hotSpotNewsParam.getUuid(),
          hotSpotNewsParam.getStartDate(), hotSpotNewsParam.getEndDate(),
          build);
      CountResponse r = elasticsearchClient.count(build);
      long count = r.count();
      log.info("【uuid: {}】 get [startDate: {} endDate: {}] count number: {}, cost: {}s", hotSpotNewsParam.getUuid(),
          hotSpotNewsParam.getStartDate(), hotSpotNewsParam.getEndDate(),
          count, timeUtil.getCost());
      return count;
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * 获取匹配给定参数的热点新闻列表。
   *
   * @param hotSpotNewsParam 热点新闻的参数
   * @return 包含热点新闻的命中列表
   */
  public List<Hit<HotSpotNews>> getNews(HotSpotNewsParam hotSpotNewsParam) {
    List<Hit<HotSpotNews>> ret = new ArrayList<>();
    int count = (int)getCount(hotSpotNewsParam);
    count = Math.min(count, hotSpotNewsParam.getMaxReadingSize());
    log.info("【uuid: {}】, actual reading size: {}", hotSpotNewsParam.getUuid(), count);
    hotSpotNewsParam.setTotalNews(count);

    int batchSize = configService.getReadBatchSize();
    int epoch = (int)(Math.ceil(count * 1.0 / batchSize));
    TimeUtil timeUtil = new TimeUtil();
    log.info("【uuid: {}】 reading from es [startDate: {} endDate: {}] start...", hotSpotNewsParam.getUuid(),
        hotSpotNewsParam.getStartDate(), hotSpotNewsParam.getEndDate()
        );
    for (int i = 0; i < epoch; i++) {
      SearchRequest.Builder builder = buildRequest(hotSpotNewsParam, i * batchSize, batchSize);
      if (hotSpotNewsParam.getVectorField().equals("t")) {
        builder.source(s->s.filter(f->f.excludes("contentVector", "titleContentVector")));
      } else if (hotSpotNewsParam.getVectorField().equals("c")) {
        builder.source(s->s.filter(f->f.excludes("vector", "titleContentVector")));
      } else {
        builder.source(s->s.filter(f->f.excludes("vector", "contentVector")));
      }
      SearchRequest build = builder.build();
      if (i == 0) {
        log.info("【uuid: {}】 reading from es [startDate: {} endDate: {}] search request template: {}", hotSpotNewsParam.getUuid(),
            hotSpotNewsParam.getStartDate(), hotSpotNewsParam.getEndDate(), build
        );
      }
      try {
        SearchResponse<HotSpotNews> r = elasticsearchClient.search(build, HotSpotNews.class);
        List<Hit<HotSpotNews>> list = r.hits().hits();
        ret.addAll(list);
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    }
    log.info("【uuid: {}】 reading from es [startDate: {} endDate: {}] reading from es end, total cost: {}s", hotSpotNewsParam.getUuid(),
        hotSpotNewsParam.getStartDate(), hotSpotNewsParam.getEndDate(), timeUtil.getCost()
    );

    return ret;
  }
}
